import os
import sys
import time
import bpy
from ogreEx.CReportSingleton import Report
from ogreEx.CSimpleSaxWriter import SimpleSaxWriter
from ogreEx.all import material_name
from ogreEx.axis import swap
from ogreEx.config import getConfig
from ogreEx.context import _USE_RPYTHON_
from ogreEx.context import  Rmesh
from ogreEx.ogre.CSkeleton import Skeleton
from ogreEx.ogre.CVertexNoPos import VertexNoPos
from ogreEx.prog.OgreXMLConverter import OgreXMLConverter
from ogreEx.ui.operator.PopUpDialogOperator import show_dialog
from ogreEx.helper import clean_object_name, timer_diff_str


def find_bone_index( ob, arm, groupidx):    # sometimes the groups are out of order, this finds the right index.
    if groupidx < len(ob.vertex_groups):        # reported by Slacker
        vg = ob.vertex_groups[ groupidx ]
        j = 0
        for i,bone in enumerate(arm.pose.bones):
            if not bone.bone.use_deform and getConfig()['ONLY_DEFORMABLE_BONES']:
                j+=1 # if we skip bones we need to adjust the id
            if bone.name == vg.name:
                return i-j
    else:
        print('WARNING: object vertex groups not in sync with armature', ob, arm, groupidx)

## Creating .mesh
def dot_mesh( ob, path='/tmp', force_name=None, ignore_shape_animation=False, normals=True ):
    start = time.time()

    if not os.path.isdir( path ):
        print('>> Creating working directory', path )
        os.makedirs( path )

    Report.meshes.append( ob.data.name )
    Report.faces += len( ob.data.tessfaces )
    Report.orig_vertices += len( ob.data.vertices )

    cleanup = False
    if ob.modifiers:
        cleanup = True
        copy = ob.copy()
        #bpy.context.scene.objects.link(copy)
        rem = []
        for mod in copy.modifiers:        # remove armature and array modifiers before collaspe
            if mod.type in 'ARMATURE ARRAY'.split(): rem.append( mod )
        for mod in rem: copy.modifiers.remove( mod )
        # bake mesh
        mesh = copy.to_mesh(bpy.context.scene, True, "PREVIEW")    # collaspe
    else:
        copy = ob
        mesh = ob.data

    name = force_name or ob.data.name
    name = clean_object_name(name)
    xmlfile = os.path.join(path, '%s.mesh.xml' % name )

    print('      - Generating:', '%s.mesh.xml' % name)
    
    if _USE_RPYTHON_ and False:
        Rmesh.save( ob, xmlfile )
    else:
        f = None
        try:
            f = open( xmlfile, 'w' )
        except Exception as e:
            show_dialog("Invalid mesh object name: " + name)
            return
            
        doc = SimpleSaxWriter(f, 'UTF-8', "mesh", {})

        # Very ugly, have to replace number of vertices later
        doc.start_tag('sharedgeometry', {'vertexcount' : '__TO_BE_REPLACED_VERTEX_COUNT__'})

        print('      - Writing shared geometry')
        doc.start_tag('vertexbuffer', {
                'positions':'true',
                'normals':'true',
                'colours_diffuse' : str(bool( mesh.vertex_colors )),
                'texture_coords' : '%s' % len(mesh.uv_textures) if mesh.uv_textures.active else '0'
        })

        # Vertex colors
        vcolors = None
        vcolors_alpha = None
        if len( mesh.tessface_vertex_colors ):
            vcolors = mesh.tessface_vertex_colors[0]
            for bloc in mesh.tessface_vertex_colors:
                if bloc.name.lower().startswith('alpha'):
                    vcolors_alpha = bloc; break

        # Materials
        materials = []
        for mat in ob.data.materials:
            if mat:
                materials.append( mat )
            else:
                print('[WARNING:] Bad material data in', ob)
                materials.append( '_missing_material_' )        # fixed dec22, keep proper index
        if not materials:
            materials.append( '_missing_material_' )
        _sm_faces_ = []
        for matidx, mat in enumerate( materials ):
            _sm_faces_.append([])

        # Textures
        dotextures = False
        uvcache = []    # should get a little speed boost by this cache
        if mesh.tessface_uv_textures.active:
            dotextures = True
            for layer in mesh.tessface_uv_textures:
                uvs = []; uvcache.append( uvs ) # layer contains: name, active, data
                for uvface in layer.data:
                    uvs.append( (uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4) )

        _sm_vertices_ = {}
        _remap_verts_ = []
        numverts = 0
        
        for F in mesh.tessfaces:
            smooth = F.use_smooth
            faces = _sm_faces_[ F.material_index ]
            # Ogre only supports triangles
            tris = []
            tris.append( (F.vertices[0], F.vertices[1], F.vertices[2]) )
            if len(F.vertices) >= 4: tris.append( (F.vertices[0], F.vertices[2], F.vertices[3]) )
            if dotextures:
                a = []; b = []
                uvtris = [ a, b ]
                for layer in uvcache:
                    uv1, uv2, uv3, uv4 = layer[ F.index ]
                    a.append( (uv1, uv2, uv3) )
                    b.append( (uv1, uv3, uv4) )

            for tidx, tri in enumerate(tris):
                face = []
                for vidx, idx in enumerate(tri):
                    v = mesh.vertices[ idx ]

                    if smooth:
                        nx,ny,nz = swap( v.normal )     # fixed june 17th 2011
                    else:
                        nx,ny,nz = swap( F.normal )

                    r = 1.0
                    g = 1.0
                    b = 1.0
                    ra = 1.0
                    if vcolors:
                        k = list(F.vertices).index(idx)
                        r,g,b = getattr( vcolors.data[ F.index ], 'color%s'%(k+1) )
                        if vcolors_alpha:
                            ra,ga,ba = getattr( vcolors_alpha.data[ F.index ], 'color%s'%(k+1) )
                        else:
                            ra = 1.0

                    # Texture maps
                    vert_uvs = []
                    if dotextures:
                        for layer in uvtris[ tidx ]:
                            vert_uvs.append(layer[ vidx ])

                    ''' Check if we already exported that vertex with same normal, do not export in that case,
                        (flat shading in blender seems to work with face normals, so we copy each flat face'
                        vertices, if this vertex with same normals was already exported,
                        todo: maybe not best solution, check other ways (let blender do all the work, or only
                        support smooth shading, what about seems, smoothing groups, materials, ...)
                    '''
                    vert = VertexNoPos(numverts, nx, ny, nz, r, g, b, ra, vert_uvs)
                    alreadyExported = False
                    if idx in _sm_vertices_:
                        for vert2 in _sm_vertices_[idx]:
                            #does not compare ogre_vidx (and position at the moment)
                            if vert == vert2:
                                face.append(vert2.ogre_vidx)
                                alreadyExported = True
                                #print(idx,numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "already exported")
                                break
                        if not alreadyExported:
                            face.append(vert.ogre_vidx)
                            _sm_vertices_[idx].append(vert)
                            #print(numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "appended")
                    else:
                        face.append(vert.ogre_vidx)
                        _sm_vertices_[idx] = [vert]
                        #print(idx, numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "created")

                    if alreadyExported:
                        continue

                    numverts += 1
                    _remap_verts_.append( v )

                    x,y,z = swap(v.co)        # xz-y is correct!

                    doc.start_tag('vertex', {})
                    doc.leaf_tag('position', {
                            'x' : '%6f' % x,
                            'y' : '%6f' % y,
                            'z' : '%6f' % z
                    })

                    doc.leaf_tag('normal', {
                            'x' : '%6f' % nx,
                            'y' : '%6f' % ny,
                            'z' : '%6f' % nz
                    })

                    if vcolors:
                        doc.leaf_tag('colour_diffuse', {'value' : '%6f %6f %6f %6f' % (r,g,b,ra)})

                    # Texture maps
                    if dotextures:
                        for uv in vert_uvs:
                            doc.leaf_tag('texcoord', {
                                    'u' : '%6f' % uv[0],
                                    'v' : '%6f' % (1.0-uv[1])
                            })

                    doc.end_tag('vertex')

                faces.append( (face[0], face[1], face[2]) )

        del(_sm_vertices_)
        Report.vertices += numverts

        doc.end_tag('vertexbuffer')
        doc.end_tag('sharedgeometry')
        print('        Done at', timer_diff_str(start), "seconds")
        
        print('      - Writing submeshes')
        doc.start_tag('submeshes', {})
        for matidx, mat in enumerate( materials ):
            if not len(_sm_faces_[matidx]):
                Report.warnings.append( 'BAD SUBMESH "%s": material %r, has not been applied to any faces - not exporting as submesh.' % (ob.name, mat.name) )
                continue	# fixes corrupt unused materials

            doc.start_tag('submesh', {
                    'usesharedvertices' : 'true',
                    'material' : material_name(mat),
                    # Maybe better look at index of all faces, if one over 65535 set to true;
                    # Problem: we know it too late, postprocessing of file needed
                    "use32bitindexes" : str(bool(numverts > 65535)),
                    "operationtype" : "triangle_list"
            })
            doc.start_tag('faces', {
                    'count' : str(len(_sm_faces_[matidx]))
            })
            for fidx, (v1, v2, v3) in enumerate(_sm_faces_[matidx]):
                doc.leaf_tag('face', {
                    'v1' : str(v1),
                    'v2' : str(v2),
                    'v3' : str(v3)
                })
            doc.end_tag('faces')
            doc.end_tag('submesh')
            Report.triangles += len(_sm_faces_[matidx])
        del(_sm_faces_)
        doc.end_tag('submeshes')

        ## by MrMagne
        doc.start_tag('submeshnames', {})
        for matidx, mat in enumerate( materials ):
            doc.start_tag('submesh', {
                    'name' : material_name(mat),
                    'index' : str(matidx)
            })
            doc.end_tag('submesh')
        doc.end_tag('submeshnames')
        # -- end of MrMagne's patch
        print('        Done at', timer_diff_str(start), "seconds")
        
        arm = ob.find_armature()
        if arm:
            doc.leaf_tag('skeletonlink', {
                    'name' : '%s.skeleton' % name
            })
            doc.start_tag('boneassignments', {})
            badverts = 0
            for vidx, v in enumerate(_remap_verts_):
                check = 0
                for vgroup in v.groups:
                    if vgroup.weight > getConfig()['TRIM_BONE_WEIGHTS']:
                        bnidx = find_bone_index(copy,arm,vgroup.group)
                        if bnidx is not None:        # allows other vertex groups, not just armature vertex groups
                            doc.leaf_tag('vertexboneassignment', {
                                    'vertexindex' : str(vidx),
                                    'boneindex' : str(bnidx),
                                    'weight' : str(vgroup.weight)
                            })
                            check += 1
                if check > 4:
                    badverts += 1
                    print('WARNING: vertex %s is in more than 4 vertex groups (bone weights)\n(this maybe Ogre incompatible)' %vidx)
            if badverts:
                Report.warnings.append( '%s has %s vertices weighted to too many bones (Ogre limits a vertex to 4 bones)\n[try increaseing the Trim-Weights threshold option]' %(mesh.name, badverts) )
            doc.end_tag('boneassignments')

        # Updated June3 2011 - shape animation works
        if getConfig()['SHAPE_ANIM'] and ob.data.shape_keys and len(ob.data.shape_keys.key_blocks):
            print('      - Writing shape keys')

            doc.start_tag('poses', {})
            for sidx, skey in enumerate(ob.data.shape_keys.key_blocks):
                if sidx == 0: continue
                if len(skey.data) != len( mesh.vertices ):
                    failure = 'FAILED to save shape animation - you can not use a modifier that changes the vertex count! '
                    failure += '[ mesh : %s ]' %mesh.name
                    Report.warnings.append( failure )
                    print( failure )
                    break

                doc.start_tag('pose', {
                        'name' : skey.name,
                        # If target is 'mesh', no index needed, if target is submesh then submesh identified by 'index'
                        #'index' : str(sidx-1),
                        #'index' : '0',
                        'target' : 'mesh'
                })

                for vidx, v in enumerate(_remap_verts_):
                    pv = skey.data[ v.index ]
                    x,y,z = swap( pv.co - v.co )
                    #for i,p in enumerate( skey.data ):
                    #x,y,z = p.co - ob.data.vertices[i].co
                    #x,y,z = swap( ob.data.vertices[i].co - p.co )
                    #if x==.0 and y==.0 and z==.0: continue        # the older exporter optimized this way, is it safe?
                    doc.leaf_tag('poseoffset', {
                            'x' : '%6f' % x,
                            'y' : '%6f' % y,
                            'z' : '%6f' % z,
                            'index' : str(vidx)     # is this required?
                    })
                doc.end_tag('pose')
            doc.end_tag('poses')
            print('        Done at', timer_diff_str(start), "seconds")

            if ob.data.shape_keys.animation_data and len(ob.data.shape_keys.animation_data.nla_tracks):
                print('      - Writing shape animations')
                doc.start_tag('animations', {})
                _fps = float( bpy.context.scene.render.fps )
                for nla in ob.data.shape_keys.animation_data.nla_tracks:
                    for idx, strip in enumerate(nla.strips):
                        doc.start_tag('animation', {
                                'name' : strip.name,
                                'length' : str((strip.frame_end-strip.frame_start)/_fps)
                        })
                        doc.start_tag('tracks', {})
                        doc.start_tag('track', {
                                'type' : 'pose',
                                'target' : 'mesh'
                                # If target is 'mesh', no index needed, if target is submesh then submesh identified by 'index'
                                #'index' : str(idx)
                                #'index' : '0'
                        })
                        doc.start_tag('keyframes', {})
                        for frame in range( int(strip.frame_start), int(strip.frame_end)+1, bpy.context.scene.frame_step):#thanks to Vesa
                            bpy.context.scene.frame_set(frame)
                            doc.start_tag('keyframe', {
                                    'time' : str((frame-strip.frame_start)/_fps)
                            })
                            for sidx, skey in enumerate( ob.data.shape_keys.key_blocks ):
                                if sidx == 0: continue
                                doc.leaf_tag('poseref', {
                                        'poseindex' : str(sidx-1),
                                        'influence' : str(skey.value)
                                })
                            doc.end_tag('keyframe')
                        doc.end_tag('keyframes')
                        doc.end_tag('track')
                        doc.end_tag('tracks')
                        doc.end_tag('animation')
                doc.end_tag('animations')
                print('        Done at', timer_diff_str(start), "seconds")

        ## Clean up and save
        #bpy.context.scene.meshes.unlink(mesh)
        if cleanup:
            #bpy.context.scene.objects.unlink(copy)
            copy.user_clear()
            bpy.data.objects.remove(copy)
            mesh.user_clear()
            bpy.data.meshes.remove(mesh)
            del copy
            del mesh

        del _remap_verts_
        del uvcache
        doc.close()     # reported by Reyn
        f.close()

        print('      - Created .mesh.xml at', timer_diff_str(start), "seconds")

    # todo: Very ugly, find better way
    def replaceInplace(f,searchExp,replaceExp):
            import fileinput
            for line in fileinput.input(f, inplace=1):
                if searchExp in line:
                    line = line.replace(searchExp,replaceExp)
                sys.stdout.write(line)
            fileinput.close()   # reported by jakob

    replaceInplace(xmlfile, '__TO_BE_REPLACED_VERTEX_COUNT__' + '"', str(numverts) + '"' )#+ ' ' * (ls - lr))
    del(replaceInplace)

    # Start .mesh.xml to .mesh convertion tool
    OgreXMLConverter(xmlfile, has_uvs=dotextures)

    if arm and getConfig()['ARM_ANIM']:
        skel = Skeleton( ob )
        data = skel.to_xml()
        name = force_name or ob.data.name
        name = clean_object_name(name)
        xmlfile = os.path.join(path, '%s.skeleton.xml' % name)        
        f = open( xmlfile, 'wb' )
        f.write( bytes(data,'utf-8') )
        f.close()
        OgreXMLConverter( xmlfile )

    mats = []
    for mat in materials:
        if mat != '_missing_material_': mats.append( mat )

    print('      - Created .mesh in total time', timer_diff_str(start), 'seconds')
    return mats

## end dot_mesh ##
def export_mesh(ob, path='/tmp', force_name=None, ignore_shape_animation=False, normals=True):
    ''' returns materials used by the mesh '''
    return dot_mesh( ob, path, force_name, ignore_shape_animation, normals )
